/**
 * Ajax requests processing API
 * 
 * @author  Mykhailo Stadnyk
 * @package Jsl
 * @version 1.0
 * 
 * Usage:
 * 
 * Ajax.request( config);
 * Where: config = {
	/**
	 * URL of the request
	 * @type {String}
	url       : null,

	/**
	 * Indicating whether or not the request will be asynchronous
	 * By default: true - asynchronous
	 * NOTE: does not work for cross-domain requests - true always
	 * @type {Boolean}
	async     : true,

	/**
	 * User name for authentication if required
	 * @type {String}
	user      : null,

	/**
	 * Password for authentication if required
	 * @type {String}
	pass      : null,

	/**
	 * Request method
	 * Could be one of the following:
	 *  - GET (Supported by IE7+, Mozilla 1+)
	 *  - POST (Supported by IE7+, Mozilla 1+)
	 *  - HEAD (Supported by IE7+)
	 *  - PUT
	 *  - DELETE
	 *  - OPTIONS (Supported by IE7+)
	 *  NOTE: for cross-domain requests GET always
	 * @type   {String}
	method    : 'post',

	/**
	 * Tupe of a value bypassed into success handler
	 * Accepted one of the following types:
	 *  - xhr [Default] - an XMLHttpRequest response object itself
	 *  - text          - responseText value
	 *  - json          - encoded JSON object recieved from a server
	 *  - xml           - responseXML DOM object
	 * @type   {String}
	type      : 'xhr', // xhr|text|json|xml

	/**
	 * Request parameters.
	 * Accepted:
	 *  - strings of format 'var1=val1&var2=val2'...
	 *  - objects { var1: val1, var2: val2 }
	 * @type   {String|Object}
	params    : '',

	/**
	 * Additional request headers in format:
	 *  { headerName: headerValue, ... }
	 *  NOTE: do not applicable for cross-domain requests
	 * @type   {Object}
	headers   : {},

	/**
	 * Request success handler
	 * @type   {Function}
	success   : function() {},

	/**
	 * Request failure handler
	 * @type   {Function}
	failure   : function() {},

	/**
	 * PHP-compatibility mode for url encoded parts
	 * Default is on (true)
	 * @type {Boolean}
	phpCompat : true
 }

 * 
 * For the list of available config options @see Jsl.Ajax.Request public properties
 */
Jsl.Ajax = Jsl.extend( Jsl.Observable, function() {

	var
	/**
	 * Private members
	 */
	_count    = 0,
	_requests = {},

	/**
	 * Parsing a params in an appropriate way, merging with extra
	 * params specified for an etire Ajax requests
	 * 
	 * @access private
	 * 
	 * @param  {String|Object} params
	 * @param  {Boolean} phpCompat
	 * @return {Jsl.Url.QueryString}
	 */
	_makeParams = function( params, phpCompat) {
		var
			extra   = Jsl.Url.parseQuery( this.extraParams || {}, phpCompat),
			request = Jsl.Url.parseQuery( params, phpCompat)
		;

		return extra.merge( request);
	},

	/**
	 * Prepears configuration object before using with request
	 * 
	 * @access private
	 * 
	 * @param  {Object} conf - configuration object
	 * @return {Object} conf - modified configuration object
	 */
	_prepare = function( conf) {
		conf.url = Jsl.isDefined( conf.url) ? conf.url : this.url;

		conf.method = Jsl.isDefined( conf.method) ? conf.method : this.method;
		conf.params = _makeParams.call( this, conf.params, conf.phpCompat).toString();

		if (conf.method == 'get' && conf.params)
			conf.url += (conf.url.indexOf( '?') != -1 ? '&' : '?') + conf.params
		;

		if (this.disableCache) {
			var nc = Math.random().toString().replace( /^.*?\./, '');
			conf.url += (conf.url.indexOf( '?') != -1 ? '&' : '?') + nc;
		}

		conf.timeout = Jsl.isDefined( conf.timeout) ? new Number( conf.timeout) : this.timeout;

		conf.type  = conf.type || this.dataType || 'xhr';
		conf.async = Jsl.isDefined( conf.async) ? !!conf.async : this.async;

		conf.proxy = conf.proxy || this.proxy;
		if (Jsl.isObject( conf.proxy))
			conf.proxy = new Jsl.Ajax.Proxy( conf.proxy)
		;
		if (!(conf.proxy instanceof Jsl.Ajax.Proxy))
			conf.proxy = null;
		;

		return conf;
	},

	/**
	 * XHR ready state handler, associated with Ajax object
	 * 
	 * @access private
	 */
	_onStateChange = function( xhr, conf) {
		try {
			if (xhr.readyState == 4) {
				if (this.fireEvent( 'complete', xhr)) {
					if (xhr.status == 200) {
						var arg = xhr;
						switch (conf.type) {
							case 'text' : arg = xhr.responseText; break;
							case 'xml'  : arg = xhr.responseXML; break;
							case 'json' : arg = Jsl.decode( xhr.responseText); break;
						}
						if (this.fireEvent( 'success', xhr))
							Jsl.isFunction( conf.success) && conf.success( arg)
						;
					} else {
						if (this.fireEvent( 'failure', xhr, xhr.status))
							Jsl.isFunction( conf.failure) && conf.failure( xhr, xhr.status)
						;
					}
	
					if (this.fireEvent( 'destroy', xhr)) {
						_count--;
						xhr.destroy && xhr.destroy();
						delete xhr;
					}
				}
			}
		} catch (e) {
			this.fireEvent( 'exception', e);
		}
	};

	return {
	/**
	 * Public members
	 */

	url          : '',     // default url for requests
	method       : 'post', // default method for requests
	timeout      : 30000,  // default timeout for requests
	extraHeaders : null,   // extra request headers for all requests
	extraParams  : null,   // extra params for all requests
	disableCache : true,   // use no-cache param for GET requests?
	dataType     : 'xhr',  // data type passed to success handler
	async        : true,   // use asynchronous requests by default?
	proxy        : null,   // proxy object for cross-domain Ajax requests
	phpCompat    : false,

	/**
	 * Class constructor
	 */
	constructor  : function() {

		/**
		 * Global Jsl.Ajax object events
		 */
		this.addEvents({
			/**
			 * Fires on request abort
			 * 
			 * @param {XMLHttpRequest} xhr
			 */
			abort      : true,

			/**
			 * Fires directly before XMLHttpRequest.send() method called
			 * 
			 * @param {XMLHttpRequest} xhr
			 */
			beforesend : true,

			/**
			 * Fires when HMLHttpRequest goes into complete status, but before
			 * analyzing a request status
			 * 
			 * @param {XMLHttpRequest} xhr
			 */
			complete   : true,

			/**
			 * Fires on any request which is completed and status is 200 OK
			 * before the configured success request event dispatched
			 * 
			 * @param {XMLHttpRequest} xhr
			 */
			success    : true,

			/**
			 * Fires each time request status is not 200 OK
			 * before the configured failure request event dispatched
			 * 
			 * @param {XMLHttpRequest} xhr
			 * @param {Integer} status
			 */
			failure    : true,

			/**
			 * Fires each time when request timeout exceeded
			 * 
			 * @param {XMLHttpRequest} xhr  - XMLHttpRequest instance
			 * @param {Object}              - config object applied to XHR
			 */
			timeout    : true,

			/**
			 * Fires each time when during an Ajax.request execution
			 * was caught an exception
			 * 
			 * @params {Error}
			 */
			exception  : true
		});

		Jsl.Ajax.$super.constructor.call( this);
	},

	/**
	 * Aborting a transaction havig specified id
	 * 
	 * @param  {Integer} id    - transaction identifier
	 * @return {Jsl.Ajax} this - self instance
	 */
	abort: function( id) {
		var req = this.get( id);
		if (req) {
			if (this.fireEvent( 'abort', req))
				req && req.abort && req.abort()
			;
			if (this.fireEvent( 'destroy', req)) {
				_count--;
				req.destroy && req.destroy();
				delete req;
			}
		}
		return this;
	},

	/**
	 * Returns an XMLHttpRequest transaction object by its id
	 * 
	 * @param  {Integer} id     - transaction id
	 * @return {XMLHttpRequest} - or null if transaction was not found
	 */
	get: function( id) { return _requests[id] || null; },

	/**
	 * Creates a new AJAX request
	 */
	request : function( config) {
		var config = config || {}, xhr, id = Jsl.id();
		try {
			var
				req = _prepare.call( this, config),
				/**
				 * Check for Cross-Domain request
				 */
				currUrl = new Jsl.Url(), // <- current url
				ajaxUrl = new Jsl.Url( req.url),
				isCross = ajaxUrl.host && (currUrl.host != ajaxUrl.host)
			;
			if (isCross) {
				if (!req.proxy || !req.proxy.proxyUrl)
					throw new Error( 'Jsl.Ajax.request(): request is cross-domain but proxy was not configured properly.')
				;
				req.method = req.proxy;
				xhr = new Jsl.JsHttpRequest();
			} else {
				/**
				 * XMLHttpRequest transport object
				 * @see http://www.w3.org/TR/XMLHttpRequest/#the-xmlhttprequest-interface
				 */
				xhr = new (function() {
						if (!Jsl.isDefined( window.XMLHttpRequest))
							window.XMLHttpRequest = function () {
								try { return new ActiveXObject( 'Msxml2.XMLHTTP.6.0'); } catch (e1) {}
								try { return new ActiveXObject( 'Msxml2.XMLHTTP.3.0'); } catch (e2) {}
								try { return new ActiveXObject( 'Msxml2.XMLHTTP'); }     catch (e3) {}
								throw new Error( 'This browser does not support XMLHttpRequest.');
							}
						;
						return window.XMLHttpRequest;
					}())
				;
				req.method = req.method.toUpperCase();
			}

			_requests[id] = xhr;
			_count++;
	
			xhr.open( req.method, req.url, req.async, req.user, req.pass);
	
			if (req.headers)
				for (var k in req.headers)
					xhr.setRequestHeader( k, req.headers[k])
			;
			xhr.setRequestHeader( 'X-Requested-With', 'XMLHttpRequest');
			xhr.setRequestHeader( 'X-Powered-By', 'Jsl ' + Jsl.version);
			if (req.method == 'POST') {
				xhr.setRequestHeader( 'Content-Type', 'application/x-www-form-urlencoded');
			}

			xhr.onreadystatechange = _onStateChange.bind( this, xhr, req);

			var self = this;
			if (this.fireEvent( 'beforesend', req)) {
				(function() { // fixing IE bug - call send deferring
					xhr.send( (!isCross && (req.method == 'POST')) ? (req.params || null) : null);
				}).defer( (Jsl.isIe && isCross) ? 1 : 0);
				if (req.timeout)
					setTimeout( function() {
						if (self.fireEvent( 'timeout', xhr, req))
							self.abort( id)
						;
					}, req.timeout)
				;
			}
		} catch (e) {
			this.fireEvent( 'exception', e);
		}

		return xhr.id;
	},

	/**
	 * Sets new configuration settings to global Jsl.Ajax object
	 * 
	 * @param {Object}
	 */
	configure : function( config) { Jsl.apply( this, config); },

	/**
	 * Returns a number of opened connections
	 * 
	 * @return {Integer}
	 */
	getCount : function() { return _count; }

};}());
Jsl.Ajax = new Jsl.Ajax; // singleton
